import { MathUtil } from './MathUtil';
import { Vector3 } from './Vector3';
import { Line3 } from './Line3';

/**
 * 面
 */
export class Plane {
    /** 法线 */
    public normal: Vector3;
    /** 常量 */
    public constant: number;

	/**
	 * 构造处理
	 */
    public constructor(normal?: Vector3, constant?: number) {
        this.normal = new Vector3(1, 0, 0);
        if (normal) {
            this.normal.assign(normal);
            this.normal.normalize();
        }
        this.constant = (constant !== undefined) ? constant : 0;
    }

	/**
	 * 判断是否相等。
	 */
    public equals(plane) {
        return plane.normal.equals(this.normal) && (plane.constant === this.constant);
    }

    /**
      * 用法线和平面上一点构造平面。
      *
      * @param normalref 法线
     * @param point 点
      */
    public makeWithNormalAndPoint(normalref: Vector3, point: Vector3): Plane {
        this.normal = normalref.clone();
        this.normal.normalize();
        this.constant = this.normal.dot(point);

        return this;
    }

	/**
	 * 判断是否近似相等。
	 *
	 * @param plane 面板
	 * @return 近似相等
	 */
    public nearlyEquals(plane: Plane): boolean {
        if (!MathUtil.nearlyEquals(plane.constant, this.constant)) {
            return false;
        }
        if (!plane.normal.nearlyEquals(this.normal)) {
            return false;
        }
        return true;
    }

	/**
	 * 判断是否共面。
	 *
	 * @param plane 是否共面
	 * @param tolerance 公差
	 * @return 近似相等
	 */
    public isCoplane(plane: Plane, tolerance: number = MathUtil.TOLERANCE): boolean {
        if (!MathUtil.nearlyEquals(this.constant, -plane.constant, tolerance)) {
            return false;
        }
        if (!this.normal.nearlyEqualsNegative(plane.normal, tolerance)) {
            return false;
        }

        if (!this.normal.nearlyEqualsNegative(plane.normal, tolerance) &&
            !this.normal.nearlyEquals(plane.normal, tolerance)) {
            return false;
        }
        return true;
    }

	/**
	 * 设置处理
	 */
    public set(normal: Vector3, constant: number) {
        this.normal.assign(normal);
        this.constant = constant;
        return this;
    }

	/**
	 * 复制数据。
	 */
    public assign(plane: Plane) {
        this.normal.assign(plane.normal);
        this.constant = plane.constant;
        return this;
    }

	/**
	 * 复制数据。
	 */
    public copy(plane: Plane) {
        this.normal.assign(plane.normal);
        this.constant = plane.constant;
        return this;
    }

	/**
	 * 设置组件处理
	 */
    public setComponents(x: number, y: number, z: number, w: number) {
        this.normal.set(x, y, z);
        this.constant = w;
        return this;
    }

	/**
	 * 设置点。
	 */
    public setFromNormalAndCoplanarPoint(normal: Vector3, point: Vector3) {
        this.normal.assign(normal);
        this.constant = -point.dot(this.normal);
        return this;
    }

	/**
	 * 单位化。
	 */
    public normalize() {
        var inverseNormalLength = 1.0 / this.normal.length();
        this.normal.multiplyScalar(inverseNormalLength);
        this.constant *= inverseNormalLength;
        return this;
    }

	/**
	 * 获得负面。
	 */
    public negate() {
        this.constant *= - 1;
        this.normal.negate();
        return this;
    }

	/**
	 * 获得点距离。
	 */
    public distanceToPoint(point) {
        return this.normal.dot(point) + this.constant;
    }

    /**
     * 点到平面的距离。
     * 
     * @param point 点
     */
    public distanceToPoint_v2(point) {
        return this.normal.dot(point) - this.constant;
    }

	/**
	 * 获得球距离。
	 */
    public distanceToSphere(sphere) {
        return this.distanceToPoint(sphere.center) - sphere.radius;
    }

	/**
	 * 获得点投影。
	 */
    public projectPoint(point, optionalTarget) {
        return this.orthoPoint(point, optionalTarget).sub(point).negate();
    }

	/**
	 * 获得齐次投影。
	 */
    public orthoPoint(point, optionalTarget) {
        var perpendicularMagnitude = this.distanceToPoint(point);
        var result = optionalTarget || new Vector3();
        return result.assign(this.normal).multiplyScalar(perpendicularMagnitude);
    }

	/**
	 * 是否与线相交。
	 */
    public intersectsLine(line) {
        var startSign = this.distanceToPoint(line.start);
        var endSign = this.distanceToPoint(line.end);
        return (startSign < 0 && endSign > 0) || (endSign < 0 && startSign > 0);
    }

	/**
	 * 是否与盒子相交。
	 */
    public intersectsBox(box) {
        return box.intersectsPlane(this);
    }

	/**
	 * 是否与球体相交。
	 */
    public intersectsSphere(sphere) {
        return sphere.intersectsPlane(this);
    }


	/**
	  * 判断与另一个测试平面是否相交
	  * @param other 测试平面
	  * @param ray3 这两个平面的相交线
	  * @return 如果平行或重合 返回false,相交true
	  */
    public intersectPlane(other: Plane): Line3 {
        var invdet: number;
        var dir2 = new Vector3();
        var xpt = new Vector3();
        var xdir = this.normal.crossVector(other.normal) as Vector3;

        dir2.x = xdir.x * xdir.x;
        dir2.y = xdir.y * xdir.y;
        dir2.z = xdir.z * xdir.z;

        var pl1n = this.normal as Vector3;
        var pl2n = other.normal as Vector3;
        var pl1w = -this.constant;
        var pl2w = -other.constant;

        //在XY平面上拾取一点
        if (dir2.z > dir2.y && dir2.z > dir2.x && dir2.z > MathUtil.EPSILON) {
            invdet = 1. / xdir.z;
            xpt.x = pl1n.z * pl2w - pl2n.z * pl1w;
            xpt.y = 0;
            xpt.z = pl2n.x * pl1w - pl1n.x * pl2w;
        }
        //在XZ平面上拾取一点
        else if (dir2.y > dir2.x && dir2.x > MathUtil.EPSILON) {
            invdet = -1. / xdir.y;
            xpt.x = pl1n.z * pl2w - pl2n.z * pl1w;
            xpt.y = 0;
            xpt.z = pl2n.x * pl1w - pl1n.x * pl2w;
        }
        //在YZ平面上拾取一点
        else if (dir2.x > MathUtil.EPSILON) {
            invdet = 1. / xdir[0];
            xpt.x = 0;
            xpt.y = pl1n.z * pl2w - pl2n.z * pl1w;
            xpt.z = pl2n.y * pl1w - pl1n.y * pl2w;
        }
        else {
            return null;
        }

        xpt = xpt.multiplyScalar(invdet);

        invdet = 1. / (Math.sqrt(dir2.x + dir2.y + dir2.z));
        xdir = xdir.multiplyScalar(invdet);

        var line = new Line3();
        line.from = xpt;
        line.dir = xdir;

        return line;
    }


	/**
	 * 移动处理。
	 */
    public translate(offset) {
        this.constant = this.constant - offset.dot(this.normal);
        return this;
    }

	/**
	 * 应用矩阵。
	 */
    // public applyMatrix4(matrix, optionalNormalMatrix?) {
    //    var v1 = MemoryUtil.alloc(Vector3) as Vector3;
    //    var v2 = MemoryUtil.alloc(Vector3) as Vector3;
    //    var m1 = MemoryUtil.alloc(Matrix3) as Matrix3;
    //    var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix(matrix);
    //    var newNormal = (v1.assign(this.normal) as any).applyMatrix3(normalMatrix);
    //    var newCoplanarPoint = this.coplanarPoint(v2) as any;
    //    newCoplanarPoint.applyMatrix4(matrix);
    //    this.setFromNormalAndCoplanarPoint(newNormal, newCoplanarPoint);
    //    MemoryUtil.free(v1);
    //    MemoryUtil.free(v2);
    //    MemoryUtil.free(m1);
    //    return this;
    // }

	/**
	 * 克隆对象。
	 */
    public clone(): Plane {
        return new (this as any).constructor().copy(this);
    }
}
